﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine.NextGen
{

    public class PathIntegrator : IEnumerable<PathSampler>
    {
        public RayEngineScene Scene;
        public IPrimarySpaceSampler Sampler;
        public long statsRenderingStart;
        public long statsTotalSampleCount;
        protected ImageFilmBase film;
        protected SurfaceSampler surfaceSampler;

        protected SampleBuffer sampleBuffer;

        protected int firstPath, lastPath, maxPaths;
        protected List<PathSampler> paths;

        internal bool volumePaths = false; // TODO 

        public PathIntegrator()
        {
            volumePaths = false;
        }

        public virtual void Init(int maxPaths, RayEngineScene scene, ImageFilmBase agg, IPrimarySpaceSampler sm, SurfaceSampler ss)
        {
            this.maxPaths = maxPaths;
            this.surfaceSampler = ss;
            this.Sampler = sm;
            this.Scene = scene;
            this.film = agg;
            this.sampleBuffer = film.GetFreeSampleBuffer();
            this.paths = new List<PathSampler>(scene.MaxPaths + 1);
        }

        internal virtual void ReInit()
        {
            for (var i = 0; i < paths.Count; ++i)
                paths[i].InitPath(this);
            firstPath = 0;
            film.ResetProcessor();

            statsRenderingStart = DateTime.UtcNow.Ticks / 1000;
            statsTotalSampleCount = 0;
        }

        internal void ClearPaths()
        {
            paths.Clear();
        }

        internal virtual void FillRayBuffer(RayBuffer rayBuffer)
        {
            if (paths.Count == 0)
            {
                // Need at least 2 paths
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());
                paths[0].InitPath(this);
                paths[1].InitPath(this);
                firstPath = 0;
            }

            bool allPathDone = true;
            lastPath = firstPath;
            for (; ; )
            {
                if (!paths[lastPath].FillRayBuffer(rayBuffer))
                {
                    // Not enough free space in the RayBuffer
                    allPathDone = false;
                    break;
                }

                lastPath = (lastPath + 1) % paths.Count;
                if (lastPath == firstPath)
                    break;
            }

            if (allPathDone)
            {
                var newPaths = 0;
                var maxNewPaths = maxPaths;

                for (; ; )
                {
                    newPaths++;
                    if (newPaths > maxNewPaths)
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 1;
                        break;
                    }

                    var p = this.CreatePathSampler();
                    paths.Add(p);
                    p.InitPath(this);
                    if (!p.FillRayBuffer(rayBuffer))
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 2;
                        break;
                    }
                }
            }
        }

        internal virtual void AdvancePaths(RayBuffer rayBuffer)
        {
#if VERBOSE
            try
            {
#endif
                for (int i = firstPath; i != lastPath; i = (i + 1)%paths.Count)
                {
                    paths[i].Advance(rayBuffer, this.sampleBuffer, this.OnSplat);

                    if (this.sampleBuffer.IsFull())
                    {
                        this.statsTotalSampleCount += this.sampleBuffer.GetSampleCount();
                        // Splat all samples on the film
                        this.film.SplatSampleBuffer(true, sampleBuffer);
                        this.sampleBuffer = this.film.GetFreeSampleBuffer();
                    }
                }

                firstPath = (lastPath + 1)%paths.Count;

#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#endif
            /*
            foreach (var pathSampler in paths) {
                if (pathSampler.RayIndex > 0)
                    pathSampler.Advance(rayBuffer, film, item => item.InitPath(this));
            }*/
        }

        protected virtual void OnSplat(PathSampler item)
        {
            item.InitPath(this);
        }

        protected virtual PathSampler CreatePathSampler()
        {
            throw new NotImplementedException();
        }
        public IEnumerator<PathSampler> GetEnumerator()
        {
            return this.paths.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
